(function (fruit, cache, support, browser, util, global, document) {
    var rmustAppend = /<(?:object|embed|option|style)/i;
    var ralpha = /alpha\([^)]*\)/i,
        ropacity = /opacity=([^)]*)/,
        rUnit = /^[+-]?[\d\.]+/g,
        rsizeElement = /width|height|top|right|bottom|left|size|margin|padding/i,
        cssNumber = ["z-index", "font-weight", "opacity", "line-height"],
        PX = 'px',
        AUTO = 'auto';
    var DEBUG = fruit.DEBUG;
    var attrHooks = {
        "class":"className",
        "for":"htmlFor",
        tabindex:"tabIndex",
        selectedindex:"selectedIndex",
        readonly:"readOnly",
        maxlength:"maxLength",
        cellspacing:"cellSpacing",
        cellpadding:"cellPadding",
        rowspan:"rowSpan",
        colspan:"colSpan",
        usemap:"useMap",
        frameborder:"frameBorder",
        contenteditable:"contentEditable",
        "style":{
            setter:function (inElement, inValue) {
                inElement.style.cssText += ";" + inValue;
            },
            getter:function (inElement) {
                return inElement.style.cssText;
            }
        },
        "text":{
            setter:(function () {
                if (support.innerText) {
                    return function (inElement, inText) {
                        inElement.innerText = inText;
                    };
                } else {
                    return function (inElement, inText) {
                        inElement.textContent = inText;
                    };
                }
            }()),
            getter:(function () {
                if (support.innerText) {
                    return function (inElement) {
                        return inElement.innerText || inElement.nodeValue;
                    };
                } else {
                    return function (inElement) {
                        return inElement.textContent;
                    };
                }
            }())
        },
        "disabled":{
            setter:function (inElement, inValue) {
                if (inValue) {
                    inElement.setAttribute("disabled", "disabled");
                } else {
                    inElement.removeAttribute("disabled");
                }
            },
            getter:function (inElement) {
                return !!inElement.getAttribute("disabled");
            }
        },
        "html":{
            setter:function (inElement, inHtml) {
                if (!rmustAppend.test(inElement.tagName)) {
                    inElement.innerHTML = inHtml;
                } else {
                    dom.empty(inElement);
                    inElement.appendChild(dom.createElement(inHtml));
                }
            },
            getter:function (inElement) {
                return inElement.innerHTML;
            }
        },
        "value":{
            _fireEvent:function (inElement) {
                //TODO:checked compabilty
                var evt = document.createEvent("HTMLEvents");
                evt.initEvent("change", false, true);
                inElement.dispatchEvent(evt);
            },
            setter:function (inElement, inValue) {
                if ('value' in inElement) {
                    var type = inElement.type;
                    switch (type) {
                        case 'checkbox':
                        case 'radio':
                            inElement.checked = !!inValue;
                            break;
                        case 'select-one':
                            inElement.selectedIndex = inValue;
                            break;
                        default:
                            inElement.value = inValue;
                    }
                    this._fireEvent(inElement);
                }
            },
            getter:function (inElement) {
                var type = inElement.type;
                var val = inElement.value;
                switch (type) {
                    case 'checkbox':
                    case 'radio':
                        val = inElement.checked;
                        break;
                    default:
                        val = inElement.value;
                }
                return val;
            }
        }
    };
    var getStyle = (function () {
        if (support.getComputedStyle) {
            return function (inElement, inName) {
                var style = global.getComputedStyle(inElement, null);
                return style && style.getPropertyValue(inName);
            };
        } else {
            return function (inElement, inName) {
                var style = inElement.currentStyle;
                if (style) {
                    return style[inName];
                }
            };
        }
    }());
    var removeStyle = (function () {
        if (support.removeProperty) {
            return function (inElement, inName) {
                return inElement.style.removeProperty(inName);
            };
        } else {
            return function (inElement, inName) {
                return inElement.style.removeAttribute(inName);
            };
        }
    }());
    var siblingMap = {
        'firstChild':['firstElementChild', 'firstChild', 'nextSibling'],
        'lastChild':['lastElementChild', 'lastChild', 'previousSibling'],
        'previousSibling':['previousElementSibling', 'previousSibling', 'previousSibling'],
        'nextSibling':['nextElementSibling', 'nextSibling', 'nextSibling']
    };
    var sibling = (function () {
        if (support.firstElementChild) {
            return function (element, traversings) {
                return element[traversings[0]];
            };
        } else {
            return function (element, traversings) {
                var node = element[traversings[1]];
                if (dom.isElement(node)) {
                    return node;
                } else {
                    while (node && (node = node[traversings[2]])) {
                        if (node.nodeType == 1) {
                            return node;
                        }
                    }
                    return null;
                }
            };
        }
    }());

    function convertPixelValue(inElement, inValue) {
        var style = inElement.style,
            left = style.left,
            _temp = inElement.runtimeStyle.left,
            pixelValue;
        inElement.runtimeStyle.left = inElement.currentStyle.left;
        style.left = inValue || 0;
        pixelValue = style.pixelLeft;
        style.left = left;
        inElement.runtimeStyle.left = _temp;
        return pixelValue;
    }

    function getUnit(inName, inValue) {
        if (inValue) {
            var unit = inValue.toString().replace(rUnit, '');
            if (unit && unit !== inValue) {
                return unit;
            }
        }
        return util.contains(cssNumber, inName) ? '' : PX;
    }

    function getClsPos(inElement, inClassName) {
        return (' ' + inElement.className + ' ').indexOf(' ' + inClassName + ' ');
    }

    function normalizeResult(inElement, inName, inValue) {
        var parentValue, unit;
        if (rsizeElement.test(inName) && inValue !== AUTO) {
            unit = getUnit(inName, inValue);
            switch (unit) {
                case 'px':
                    return inValue;
                case '%':
                    parentValue = dom.getStyle(inElement.parentNode, inName);
                    return parseFloat(parentValue) * parseFloat(inValue) / 100 + PX;
                default :
                    return convertPixelValue(inElement, inValue) + PX;
            }
        }
        return inValue;
    }

    var cssHooks = {
        'float':{
            getter:(function () {
                if (support.cssFloat) {
                    return function (inElement, inIsComputed) {
                        if (inIsComputed) {
                            var style = inElement.ownerDocument.defaultView.getComputedStyle(inElement, null);
                            return style ? style.getPropertyValue('float') : null;
                        } else {
                            return inElement.style.cssFloat;
                        }
                    };
                } else {
                    return function (inElement, inIsComputed) {
                        return inElement[inIsComputed ? 'currentStyle' : 'style'].styleFloat;
                    };
                }
            }()),
            setter:(function () {
                if (support.cssFloat) {
                    return function (inElement, inValue) {
                        return inElement.style.cssFloat = inValue;
                    };
                } else {
                    return function (inElement, inValue) {
                        return inElement.style.styleFloat = inValue;
                    };
                }
            }())
        },
        'opacity':{
            getter:(function () {
                if (!support.opacity) {
                    return function (inElement, inIsComputed) {
                        var hasOpacity, filter, defaultOpacity;
                        filter = inElement[inIsComputed ? 'currentStyle' : 'style'].filter || "";
                        hasOpacity = ropacity.test(filter || "");
                        defaultOpacity = inIsComputed ? 1 : "";
                        return hasOpacity ? ( parseFloat(RegExp.$1) / 100 ) + "" : defaultOpacity;
                    };
                }
            }()),
            setter:(function () {
                if (!support.opacity) {
                    return function (inElement, inValue) {
                        var style = inElement.style,
                            currentStyle = inElement.currentStyle;
                        var opacity = "alpha(opacity=" + inValue * 100 + ")",
                            filter = currentStyle && currentStyle.filter || style.filter || "";
                        style.filter = ralpha.test(filter) ?
                            filter.replace(ralpha, opacity) :
                            filter + " " + opacity;
                        if (!inElement.currentStyle.hasLayout) {
                            inElement.style.zoom = 1;
                        }
                    };
                }
            }()),
            remove:(function () {
                if (!support.opacity) {
                    return function (inElement) {
                        var style = inElement.style,
                            currentStyle = inElement.currentStyle,
                            filter = currentStyle && currentStyle.filter || style.filter || "";
                        if (ralpha.test(filter)) {
                            style.filter = filter.replace(ralpha, '');
                        }
                    };
                }
            }())
        }
    };
    var dom = fruit.dom = {
        byId:function (id) {
            return document.getElementById(id);
        },
        identity:(function () {
            var id = 1;
            return function (obj) {
                if (obj) {
                    var hash = obj.$id;
                    if (!hash) {
                        hash = obj.$id = id++;
                        if (DEBUG) {
                            dom.setAttribute(obj, 'data-fruit-element-id', id);
                        }
                    }
                    return hash;
                }
                else {
                    return null;
                }
            };
        }()),
        isHidden:function (inElement) {
            var width = inElement.offsetWidth;
            return width === 0 || dom.getStyle(inElement, 'visibility') === 'hidden';
        },
        isVisible:function (inElement) {
            return !dom.isHidden(inElement);
        },
        getRoot:function (inDocument) {
            var doc = inDocument || document;
            return browser.isStrict ? doc.documentElement : doc.body
        },
        getDocument:function (inElement) {
            var doc = document;
            if (inElement) {
                doc = (inElement.nodeType === 9) ? inElement : // element === document
                    inElement.ownerDocument || // element === DOM node
                        inElement.document;// element === window
            }
            return doc;
        },
        getWindow:function (element) {
            var doc = dom.getDocument(element);
            return doc.defaultView || doc.parentWindow || global;
        },
        show:function (inElement) {
            if (dom.isHidden(inElement)) {
                var inlineDisplay = cache.data(inElement, '__inlineDisplay');
                var computedDisplay = cache.data(inElement, '__computedDisplay');
                inElement.style.display = inlineDisplay ? inlineDisplay : (computedDisplay || '');
            }
        },
        hide:function (inElement) {
            cache.data(inElement, '__inlineDisplay', dom.getInlineStyle(inElement, 'display'));
            cache.data(inElement, '__computedDisplay', dom.getStyle(inElement, 'display'));
            inElement.style.display = 'none';
        },
        toggle:function (inElement) {
            if (dom.isHidden(inElement)) {
                dom.show(inElement);
            } else {
                dom.hide(inElement);
            }
        },
        isElement:function (inElement) {
            return !!inElement && inElement.nodeType == 1;
        },
        //Attribute:
        setAttribute:function (inElement, inName, inValue) {
            var name = inName.toLowerCase();
            var hook = attrHooks[inName];
            if (hook) {
                if (hook.setter) {
                    return hook.setter(inElement, inValue);
                }
                name = hook;
            }
            if (name in inElement) {
                return inElement[name] = inValue;
            } else {
                return inElement.setAttribute(name, inValue);
            }
        },
        setAttributes:function (inElement, inAttributes) {
            util.each(inAttributes, function (name, value) {
                dom.setAttribute(inElement, name, value);
            });
        },
        getAttribute:function (inElement, inName) {
            var name = inName.toLowerCase();
            var hook = attrHooks[name];
            if (hook) {
                if (hook.getter) {
                    return hook.getter(inElement);
                }
                name = hook;
            }
            if (name in inElement) {
                return inElement[name];
            } else {
                return inElement.getAttribute(name);
            }
        },
        removeAttribute:function (inElement, inName) {
            var name = inName.toLowerCase();
            var hook = attrHooks[name];
            if (hook) {
                if (hook.remove) {
                    return hook.remove(inElement);
                }
            }
            return inElement.removeAttribute(inName);
        },
        //Value,text,html:
        setValue:function (inElement, inValue) {
            attrHooks.value.setter(inElement, inValue);
        },
        setText:function (inElement, inText) {
            attrHooks.text.setter(inElement, inText);
        },
        setHtml:function (inElement, inHtml) {
            attrHooks.html.setter(inElement, inHtml);
        },
        getValue:function (inElement) {
            return attrHooks.value.getter(inElement);
        },
        getText:function (inElement) {
            return attrHooks.text.getter(inElement);
        },
        getHtml:function (inElement) {
            return attrHooks.html.getter(inElement);
        },
        //Class:
        hasClass:function (inElement, inClassName) {
            return getClsPos(inElement, inClassName) > -1;
        },
        addClass:function (inElement, inClassName) {
            if (!dom.hasClass(inElement, inClassName)) {
                var curCls = inElement.className;
                return inElement.className = curCls ? (curCls + ' ' + inClassName) : inClassName;
            }
        },
        removeClass:function (inElement, inClassName) {
            var curCls = inElement.className,
                index = getClsPos(inElement, inClassName),
                className = inClassName;
            if (index > -1) {
                if (index == 0) {
                    if (curCls !== className) {
                        className = className + ' ';
                    }
                } else {
                    className = ' ' + className;
                }
                inElement.className = curCls.replace(className, '');
            }
        },
        toggleClass:function (inElement, inClassName) {
            if (dom.hasClass(inElement, inClassName)) {
                dom.removeClass(inElement, inClassName);
            } else {
                dom.addClass(inElement, inClassName);
            }
        },
        replaceClass:function (inElement, inOldClass, inNewClass) {
            dom.removeClass(inElement, inOldClass);
            dom.addClass(inElement, inNewClass);
        },
        getClass:function (inElement) {
            return dom.getAttribute(inElement, 'class');
        },
        setClass:function (inElement, inClassName) {
            dom.setAttribute(inElement, 'class', inClassName);
        },
        //Node traversal:
        firstChild:function (element) {
            return sibling(element, siblingMap['firstChild']);
        },
        lastChild:function (element) {
            return sibling(element, siblingMap['lastChild']);
        },
        previousSibling:function (element) {
            return sibling(element, siblingMap['previousSibling']);
        },
        nextSibling:function (element) {
            return sibling(element, siblingMap['nextSibling']);
        },
        parent:function (element) {
            var node = element.parentElement || element.parentNode;
            return (node && node.nodeType != 11) ? node : null;
        },
        children:(function () {
            if (support.firstElementChild) {
                return function (element) {
                    return element.children;
                };
            } else {
                return function (element) {
                    var result = [],
                        node = element.firstChild;
                    do {
                        if (dom.isElement(node)) {
                            result.push(node);
                        }
                    } while (node && (node = node.nextSibling));
                    return result;
                }
            }
        }()),
        childAt:function (inElement, inIndex) {
            var element = dom.firstChild(inElement);
            while (element && inIndex-- > 0) {
                element = dom.nextSibling(element);
            }
            return element;
        },
        index:function (inElement) {
            var index = 0;
            var element = inElement;
            if (dom.parent(inElement) !== null) {
                while ((element = dom.previousSibling(element)) !== null) {
                    ++index;
                }
            } else {
                index = -1;
            }
            return index;
        },
        //Node operations:
        appendChild:function (inParent, inChild) {
            if (!inParent || !inChild) return null;
            return inParent.appendChild(inChild);
        },
        prependChild:function (inElement, inNewElement, inReferenceElement) {
            if (!inNewElement || !inReferenceElement) return null;
            return inElement.insertBefore(inNewElement, inReferenceElement);
        },
        insertAt:function (inElement, inTarget, inIndex) {
            if (!inTarget) return null;
            return inElement.insertBefore(inTarget, dom.childAt(inElement, inIndex));
        },
        removeChild:function (inElement, inChild) {
            if (!inChild) return null;
            return inElement.removeChild(inChild);
        },
        removeAt:function (inElement, inIndex) {
            return dom.destroy(dom.childAt(inElement, inIndex));
        },
        destroy:(function () {
            if (support.remove) {
                return function (inElement) {
                    return inElement.remove();
                };
            } else {
                return function (inElement) {
                    var parent = dom.parent(inElement);
                    if (dom.isElement(parent)) {
                        return parent.removeChild(inElement);
                    } else {
                        return null;
                    }
                };
            }
        }()),
        empty:function (element) {
            var node;
            while (node = element.firstChild) {
                element.removeChild(node);
            }
            return element;
        },
        setStyle:function (inElement, inName, inValue) {
            var hook = cssHooks[inName],
                value = inValue;
            if (util.isNumber(inValue) && !util.contains(cssNumber, inName)) {
                value += PX;
            }
            if (hook && hook.setter) {
                return hook.setter(inElement, value);
            }
            return inElement.style[inName] = value;
        },
        setStyles:function (inElement, inStyles) {
            var styleName, styleValue, hook, cssText = [];
            for (styleName in inStyles) {
                styleValue = inStyles[styleName];
                hook = cssHooks[styleName];
                if (util.isNumber(styleValue) && !util.contains(cssNumber, styleName)) {
                    styleValue += PX;
                }
                if (hook && hook.setter) {
                    hook.setter(inElement, styleValue);
                } else {
                    cssText.push(styleName + ':' + styleValue);
                }
            }
            dom.addCssText(inElement, cssText.join(';'));
        },
        addCssText:function (inElement, inCssText) {
            inElement.style.cssText += ';' + inCssText;
        },
        getInlineStyle:function (inElement, inName) {
            var hook = cssHooks[inName];
            return (hook && hook.getter && hook.getter(inElement)) || inElement.style[inName];
        },
        getStyle:function (inElement, inName) {
            var hook = cssHooks[inName], result;
            if (hook && hook.getter) {
                result = hook.getter(inElement, true);
            } else {
                result = getStyle(inElement, inName);
            }
            return normalizeResult(inElement, inName, result);
        },
        removeStyle:function (inElement, inName) {
            var hook = cssHooks[inName];
            return (hook && hook.remove && hook.remove(inElement)) || removeStyle(inElement, inName);
        },
        createElement:(function () {
            var wrap = {
                option:[1, '<select multiple="multiple">', '</select>'],
                optgroup:[1, '<select multiple="multiple">', '</select>'],
                legend:[1, '<fieldset>', '</fieldset>'],
                thead:[1, '<table>', '</table>'],
                tbody:[1, '<table>', '</table>'],
                tfoot:[1, '<table>', '</table>'],
                tr:[2, '<table><tbody>', '</tbody></table>'],
                td:[3, '<table><tbody><tr>', '</tr></tbody></table>'],
                th:[3, '<table><tbody><tr>', '</tr></tbody></table>'],
                col:[2, '<table><colgroup>', '</colgroup></table>'],
                colgroup:[1, '<table>', '</table>'],
                _default:[0, '', '']
            };
            var temp = document.createElement('div');
            return function (html) {
                var htmlTag = html.match(/<(\w+)/i);
                if (!htmlTag) {
                    return document.createElement(html);
                }
                var wraper = wrap[htmlTag[1]] || wrap._default,
                    dep = wraper[0],
                    node = null;
                temp.innerHTML = wraper[1] + html + wraper[2];
                node = temp.firstChild;
                while (dep--) {
                    node = node.firstChild;
                }
                return node;
            };
        }()),
        contains:function (inElement, inTarget, isSelf) {
            if (inTarget) return false;
            return inElement.contains ? (inElement != inTarget || isSelf) && inElement.contains(inTarget) : !!(inElement.compareDocumentPosition(inTarget) & 16);
        },
        cloneNode:function (inElement, isDeep) {
            var deep = typeof isDeep == "undefined" ? true : isDeep;
            return inElement.cloneNode(deep);
        },
        focus:function (inElement) {
            inElement && inElement.focus();
        },
        blur:function (inElement) {
            inElement && inElement.blur();
        },
        registerAttrHook:function (inHookName, inHookValue) {
            return attrHooks[inHookName] = inHookValue;
        },
        registerCssHook:function (inHookName, inHookValue) {
            return cssHooks[inHookName] = inHookValue;
        }
    };
}(fruit, fruit.cache, fruit.support, fruit.browser, _, window, document));